/*
 * Copyright 2008-2009 Daniel Cachapa <cachapa@gmail.com>
 * 
 * This program is distributed under the terms of the GNU General Public Licence Version 3
 * The licence can be read in its entirety in the LICENSE.txt file accompaning this source code,
 * or at: http://www.gnu.org/copyleft/gpl.html
 * 
 * This file is part of WeightWatch.
 *
 * WeightWatch is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * WeightWatch is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the WeightWatch source code. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package net.cachapa.weightwatch.util;

import com.sun.lwuit.Font;
import com.sun.lwuit.Graphics;
import com.sun.lwuit.Image;
import java.util.Calendar;
import java.util.Vector;
import net.cachapa.weightwatch.Bmi;
import net.cachapa.weightwatch.Plan;
import net.cachapa.weightwatch.Preferences;
import net.cachapa.weightwatch.Value;

public class Chart {
	private static final int BG_COLOUR = 0x00ffffff;
	private static final int AXIS_COLOUR = 0x00000000;
	private static final int GUIDES_COLOUR = 0x00eeeeee;
	private static final int LIGHT_GUIDES_COLOUR = 0x00f0f0f0;
	private static final int TEXT_COLOUR = 0x00000000;
	private static final int FLOATER_LINES_COLOUR = 0x0000ff00;
	private static final int FLOATER_COLOUR = 0x0000aa00;
	private static final int TREND_COLOUR = 0x00ff0000;
	private static final int PLAN_COLOUR = 0x000000ff;
	private static final int BMI_COLOUR = 0x00aaaaaa;
	
	private static final int stringPadding = 3;
	private static final Font font = Font.createSystemFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL);
	private static final int tickSize = 2;
	private static final double minMaxLabelPadding = 0.2;
	
	public static Image generateChart(int width, int height, Vector values, Preferences prefs, MyDate startDate, MyDate endDate) {
		Image chart = Image.createImage(width, height);
		Graphics g = chart.getGraphics();
		g.setColor(BG_COLOUR);
		g.fillRect(0, 0, width, height);
		g.setAntiAliased(true);
		g.setFont(font);
		int size = values.size();
		
		if (size == 0 || endDate.isBefore(((Value)values.firstElement()).date)) {
			g.setColor(TEXT_COLOUR);
			String noDataLabel = L.l("chart_nodata");
			g.drawString(noDataLabel, width/2 - font.stringWidth(noDataLabel) / 2, height/2 - font.getHeight() / 2);
			return chart;
		}
		
		// Get the maximum and minimum values
		Value value = (Value)values.firstElement();
		double minValue = value.weight;
		double maxValue = value.weight;
		if (((Value)values.lastElement()).date.isBefore(startDate)) {
			Plan plan = prefs.getPlan();
			minValue = Math.min(plan.getValueAt(startDate), plan.getValueAt(endDate));
			maxValue = Math.max(plan.getValueAt(startDate), plan.getValueAt(endDate));
		}
		else {
			for (int i = 0; i < size; i++) {
				value = (Value)values.elementAt(i);
				minValue = Math.min(minValue, value.weight);
				minValue = Math.min(minValue, value.trend);
				maxValue = Math.max(maxValue, value.weight);
				maxValue = Math.max(maxValue, value.trend);
			}
		}
		minValue = (int)Math.floor(minValue - minMaxLabelPadding);
		maxValue += minMaxLabelPadding;
		if (maxValue - minValue < 1) {
			maxValue = minValue + 1;
		}
		
		// Compute the paddings
		int leftPadding = font.stringWidth(String.valueOf((int)maxValue)) + 2* stringPadding + 1;
		int bottomPadding = height - font.getHeight() - 2*stringPadding;
		int topPadding = font.getHeight() / 2;
		double verticalStep = (bottomPadding - topPadding) / (double) (maxValue - minValue);
		
		// Draw the x axis
		int days = startDate.differenceInDays(endDate) + 1;
		double horizontalStep = (width - leftPadding) / (double) days;
		boolean showDays = true;
		if (startDate.getMonth() != endDate.getMonth() || startDate.getYear() != endDate.getYear()) {
			showDays = false;
		}
		String label;
		int lastLabel = 0;
		double labelHalfWidth;
		MyDate labelDate = new MyDate(startDate);
		int weekday;
		double sum = leftPadding;
		g.setColor(AXIS_COLOUR);
		for (int i = 1; i <= days; i++) {
			if (showDays) {
				label = String.valueOf(labelDate.getDay());
				labelHalfWidth = font.stringWidth(label) / 2;
				if (sum - labelHalfWidth > lastLabel + stringPadding && sum + labelHalfWidth < width - stringPadding) {
					g.drawString(label, (int)(sum - labelHalfWidth), bottomPadding + stringPadding);
					lastLabel = (int)(sum + labelHalfWidth);
				}
				weekday = labelDate.getWeekday();
				if (weekday == Calendar.SUNDAY) {
					g.setColor(GUIDES_COLOUR);
					g.drawLine((int) sum, 0, (int) sum, bottomPadding - tickSize);
				}
				g.setColor(AXIS_COLOUR);
				g.drawLine((int)sum, bottomPadding, (int)sum, bottomPadding - tickSize);
			}
			else if (labelDate.getDay() == 1) {
				label = String.valueOf(labelDate.getShortMonthName());
				labelHalfWidth = font.stringWidth(label) / 2;
				if (sum - labelHalfWidth > lastLabel + stringPadding) {
					g.setColor(TEXT_COLOUR);
					g.drawString(label, (int)(sum - labelHalfWidth), bottomPadding + stringPadding);
					lastLabel = (int)(sum + labelHalfWidth);
				}
				g.setColor(GUIDES_COLOUR);
				g.drawLine((int) sum, 0, (int) sum, bottomPadding - tickSize);
				g.setColor(AXIS_COLOUR);
				g.drawLine((int)sum, bottomPadding, (int)sum, bottomPadding - tickSize);
			}
			labelDate.addDays(1);
			sum += horizontalStep;
		}
		
		// Draw the y axis
		double labelHalfHeight = font.getHeight()/2;
		lastLabel = height;
		sum = bottomPadding;
		for (int i = 0; i <= (maxValue - minValue) + 1; i++) {
			if (sum + labelHalfHeight < lastLabel - stringPadding && sum - labelHalfHeight - stringPadding > 0) {
				g.setColor(TEXT_COLOUR);
				g.drawString(String.valueOf((int)minValue + i), stringPadding, (int)(sum - labelHalfHeight));
				lastLabel = (int)(sum - labelHalfHeight);
			}
			g.setColor(AXIS_COLOUR);
			g.drawLine(leftPadding, (int)sum, leftPadding + tickSize, (int)sum);
			g.setColor(GUIDES_COLOUR);
			g.drawLine(leftPadding + tickSize, (int)sum, width, (int)sum);
			if (verticalStep > 5) {		// Draw lines for x.5 when the vertical space allows
				g.setColor(LIGHT_GUIDES_COLOUR);
				g.drawLine(leftPadding, (int)(sum - verticalStep / 2), width, (int)(sum - verticalStep / 2));
			}
			sum -= verticalStep;
		}
		
		// Draw the main axis lines
		g.setColor(AXIS_COLOUR);
		g.drawLine(leftPadding-1, 0, leftPadding-1, bottomPadding);
		g.drawLine(leftPadding-1, bottomPadding, width, bottomPadding);
		
		// Draw the BMI Lines
		g.setClip(leftPadding, 0, width, bottomPadding);
		g.setColor(BMI_COLOUR);
		if (prefs.getUserHeight() > 0) {
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.SEVERELY_UNDERWEIGHT, prefs);
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.UNDERWEIGHT, prefs);
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.NORMAL, prefs);
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.OVERWEIGHT, prefs);
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.OBESE_I, prefs);
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.OBESE_II, prefs);
			drawBmiLine(g, width, leftPadding, verticalStep, maxValue, Bmi.OBESE_III, prefs);
		}
		
		// Draw the chart lines
		Image floaterPoint = null;
		try {
			floaterPoint = Image.createImage("/floater_point.png");
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		int x, yWeight, yTrend = 0, prevX = 0, prevY = 0;
		for (int i = 0; i < size; i++) {
			value = (Value)values.elementAt(i);
			x = (int)(horizontalStep * startDate.differenceInDays(value.date)) + leftPadding;
			yWeight = (int) (verticalStep * (maxValue - value.weight)) + topPadding;
			yTrend = (int) (verticalStep * (maxValue - value.trend)) + topPadding;
			// Sinks and floaters
			g.setColor(FLOATER_LINES_COLOUR);
			g.drawLine(x, yWeight, x, yTrend);
			g.setColor(FLOATER_COLOUR);
			if (horizontalStep < 2) {
				g.drawRect(x, yWeight, 0, 0);
			}
			else {
				//g.fillRect(x - 1, yWeight - 1, 2, 2);
				g.drawImage(floaterPoint, x-1, yWeight-1);
			}
			// Trend line
			if (i == 0) {
				prevX = x;
				prevY = yTrend;
			}
			g.setColor(TREND_COLOUR);
			g.drawLine(prevX, prevY, x, yTrend);
			prevX = x;
			prevY = yTrend;
		}
		
		// Draw the plan
		Plan plan = prefs.getPlan();
		MyDate planStartDate = plan.getStartDate();
		MyDate planEndDate = plan.getEndDate();
		double startValue = plan.getStartWeight();
		double endValue = plan.getGoalWeight();
		int x1, y1, x2, y2;
		x1 = (int) (horizontalStep * (startDate.differenceInDays(planStartDate))) + leftPadding;
		y1 = (int) (verticalStep * (maxValue - startValue)) + topPadding;
		x2 = (int) (horizontalStep * (startDate.differenceInDays(planEndDate)+1)) + leftPadding;
		y2 = (int) (verticalStep * (maxValue - endValue)) + topPadding;
		g.setColor(PLAN_COLOUR);
		g.drawLine(x1, y1, x2, y2);
		if (plan.getEndDate().isBefore(endDate)) {
			g.drawLine(x2, y2, width, y2);
		}
		
		return chart;
	}
	
	private static void drawBmiLine(Graphics g, int width, int leftPadding, double verticalStep, double maxValue, double bmi, Preferences prefs) {
		g.setFont(font);
		double bmiLine = Bmi.getWeight(bmi, prefs.getUserHeight(), prefs.isMetricUnits());
		int y = (int) (verticalStep * (maxValue - bmiLine));
		String levelName = Bmi.getLevelName(bmi);
		int x = width - font.stringWidth(levelName) - stringPadding;
		g.drawString(levelName, x, y - font.getHeight());
		g.drawLine(leftPadding, y, width, y);
		levelName = Bmi.getLevelName(bmi-1);
		x = width - font.stringWidth(levelName) - stringPadding;
		g.drawString(levelName, x, y + 1);
	}
}
